package com.buaa.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.buaa.config.MapConfig;
import com.buaa.entity.Address;
import com.buaa.entity.TrafficCaseInfo;
import com.buaa.entity.TrafficCasePerson;
import com.buaa.entity.TrafficCaseWeather;
import com.buaa.mapper.AddressMapper;
import com.buaa.mapper.TrafficCaseInfoMapper;
import com.buaa.mapper.TrafficCasePersonMapper;
import com.buaa.mapper.TrafficCaseWeatherMapper;
import com.buaa.service.ITrafficCaseInfoService;
import com.buaa.vo.traffic.CaseVO;
import com.buaa.vo.traffic.TimeVO;
import com.buaa.vo.traffic.TrafficCountVO;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class TrafficCaseInfoServiceImpl extends ServiceImpl<TrafficCaseInfoMapper,TrafficCaseInfo> implements ITrafficCaseInfoService {

    /**
     * 事故人员类型
     */
    private static final Integer PERSON_TYPE = 2;

    private final TrafficCasePersonMapper trafficCasePersonMapper;

    private final AddressMapper addressMapper;

    private final TrafficCaseWeatherMapper trafficCaseWeatherMapper;

    private final MapConfig mapConfig;

    @Override
    public List<TrafficCountVO> trafficCount(Integer adcode) {
        if (adcode == 0) {
            // 获取全国省份的汇总
            return this.getCountByProvince();
        } else {
            Address address = getAddressByAdcode(adcode);
            return this.getCount(address.getId(),address.getLevel());
        }
    }

    private Address getAddressByAdcode(Integer adcode) {
        QueryWrapper<Address> queryWrapper = new QueryWrapper<>();
        Address address = addressMapper.selectOne(queryWrapper.eq("adcode", adcode), false);
        if (mapConfig.getDirectCities().contains(address.getName())) {
            // 直辖市，再往下获取一层
            address = addressMapper.selectOne(queryWrapper.eq("pid", address.getId()).eq("level", "city"));
        }
        return address;
    }

    private List<TrafficCountVO> getCountByProvince() {
        QueryWrapper<TrafficCaseInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("count(*) value,province_id").gt("province_id",0).groupBy("province_id");
        List<Map<String, Object>> list = baseMapper.selectMaps(queryWrapper);
        Set<Integer> provinceIds = list.stream().map(item -> (Integer)item.get("province_id")).collect(Collectors.toSet());
        List<Address> addresses = addressMapper.selectBatchIds(provinceIds);
        Map<Integer, Address> addressMap = addresses.stream().collect(Collectors.toMap(Address::getId, Function.identity()));
        ArrayList<TrafficCountVO> trafficCountVOs = new ArrayList<>();
        list.forEach(item -> {
            Number provinceId = (Integer)item.get("province_id");
            Number count = (long)item.get("value");
            Address province = addressMap.get(provinceId.intValue());
            TrafficCountVO trafficCountVO = new TrafficCountVO();
            trafficCountVO.setName(province.getName());
            trafficCountVO.setValue(count.intValue());
            trafficCountVOs.add(trafficCountVO);
        });
        return trafficCountVOs;
    }

    private List<TrafficCountVO> getCount(Integer addressId,String level) {
        QueryWrapper<TrafficCaseInfo> wrapper = new QueryWrapper<>();
        String field;
        if ("province".equals(level)) {
            wrapper = wrapper.select("count(*) count", "city_id").eq("province_id", addressId).gt("city_id",0).groupBy("city_id");
            field = "city_id";
        } else if ("city".equals(level)){
            wrapper = wrapper.select("count(*) count", "district_id").eq("city_id", addressId).gt("district_id",0).groupBy("district_id");
            field = "district_id";
        } else {
            wrapper = wrapper.select("count(*) count", "district_id").eq("district_id", addressId).groupBy("district_id");
            field = "district_id";
        }
        List<Map<String, Object>> list = baseMapper.selectMaps(wrapper);
        ArrayList<TrafficCountVO> trafficCountVOS = new ArrayList<>();
        Set<Integer> addressIds = list.stream().map(item -> (Integer)item.get(field)).collect(Collectors.toSet());
        if (!addressIds.isEmpty()) {
            QueryWrapper<Address> addressQueryWrapper = new QueryWrapper<>();
            List<Address> cities = addressMapper.selectList(addressQueryWrapper.in("id", addressIds));
            Map<Integer, Address> addressMap = cities.stream().collect(Collectors.toMap(Address::getId, Function.identity()));
            for (Map<String, Object> map : list) {
                TrafficCountVO trafficCountVO = new TrafficCountVO();
                Number districtId = (Integer)map.get(field);
                trafficCountVO.setName(addressMap.get(districtId.intValue()).getName());
                Number count = (long) map.get("count");
                trafficCountVO.setValue(count.intValue());
                trafficCountVOS.add(trafficCountVO);
            }
        }
        return trafficCountVOS;
    }

    @Override
    public List<TrafficCountVO> countGender(Integer adcode) {
        return this.casePersonCount("gender",adcode);
    }

    @Override
    public List<TrafficCountVO> countCareer(Integer limit,Integer adcode) {
        List<TrafficCountVO> careers = this.casePersonCount("career", adcode);
        // 将数据排序，并截取数量最大的10个
        List<TrafficCountVO> sorted = CollUtil.reverse(ListUtil.sortByProperty(careers, "value"));
        if (careers.size() <= limit) {
            return sorted;
        }
        return ListUtil.sub(sorted,0,limit);
    }

    private List<TrafficCountVO> casePersonCount(String column,Integer adcode) {
        return trafficCasePersonMapper.selectJoinList(TrafficCountVO.class,this.casePersonCountQueryWrapper(column,adcode));
    }

    private MPJLambdaWrapper<TrafficCasePerson> casePersonCountQueryWrapper(String column,Integer adcode) {
        MPJLambdaWrapper<TrafficCasePerson> wrapper = new MPJLambdaWrapper<TrafficCasePerson>().select("count(*) value,IFNULL(" + column + ",'不详') name")
                .eq(TrafficCasePerson::getPersonType, PERSON_TYPE)
                .groupBy(column);
        if (adcode != 0) {
            Address address = this.getAddressByAdcode(adcode);
            wrapper.leftJoin(TrafficCaseInfo.class, TrafficCaseInfo::getId, TrafficCasePerson::getCaseId)
                    .eq(getAddressField(address), address.getId());
        }
        return wrapper;
    }

    private String getAddressField(Address address) {
        String field;
        if ("province".equals(address.getLevel())) {
            field = "province_id";
        } else if ("city".equals(address.getLevel())){
            field = "city_id";
        } else {
            field = "district_id";
        }
        return field;
    }

    @Override
    public List<CaseVO> getLastCases(Integer limit,Integer adcode) {
        Page<TrafficCaseInfo> page = new Page<>(0, limit);
        QueryWrapper<TrafficCaseInfo> queryWrapper = new QueryWrapper<>();
        if (adcode != 0) {
            Address address = this.getAddressByAdcode(adcode);
            queryWrapper.eq(this.getAddressField(address),address.getId());
        }
        page.setRecords(list(queryWrapper.isNotNull("traffic_date").orderByDesc("traffic_date")));
        List<TrafficCaseInfo> list = page.getRecords();

        Set<Integer> ids = list.stream().map(TrafficCaseInfo::getId).collect(Collectors.toSet());

        QueryWrapper<TrafficCasePerson> trafficCasePersonQueryWrapper = new QueryWrapper<>();
        List<TrafficCasePerson> casePersonList = trafficCasePersonMapper.selectList(trafficCasePersonQueryWrapper.select("id","case_id","person_type","car_type","injury").in("case_id", ids));
        //整理事故车类型的数据,便于封装
        Map<Integer, TrafficCasePerson> carTypeMap = casePersonList.stream().filter(casePerson -> casePerson.getPersonType() != null && casePerson.getPersonType().equals(PERSON_TYPE)).collect(Collectors.toMap(TrafficCasePerson::getId, Function.identity()));
        //整理伤亡数据
        HashMap<Integer, Map<String, Integer>> injuryMap = new HashMap<>();
        for (TrafficCasePerson casePerson : casePersonList) {
            if (casePerson.getInjury() != null && !casePerson.getInjury().equals("")) {
                Map<String, Integer> mapData = injuryMap.get(casePerson.getCaseId());
                if (mapData != null) {
                    mapData.merge(casePerson.getInjury(), 1, Integer::sum);
                } else {
                    HashMap<String, Integer> data = new HashMap<>();
                    data.put(casePerson.getInjury(),1);
                    injuryMap.put(casePerson.getCaseId(),data);
                }
            }
        }
        //整理返回数据
        ArrayList<CaseVO> caseVOS = new ArrayList<>();
        for (TrafficCaseInfo trafficCaseInfo : list) {
            CaseVO caseVO = new CaseVO();
            caseVO.setDate(DateUtil.format(DateUtil.parse(trafficCaseInfo.getTrafficDate().toString()),"yyyy-MM-dd"));
            caseVO.setTime(trafficCaseInfo.getTrafficTime());
            if (StrUtil.isEmpty(trafficCaseInfo.getTrafficAddr())) {
                caseVO.setAddress(trafficCaseInfo.getCaseProvince() + trafficCaseInfo.getCaseCity() + trafficCaseInfo.getCaseArea());
            } else {
                caseVO.setAddress(trafficCaseInfo.getTrafficAddr());
            }
            TrafficCasePerson casePerson = carTypeMap.get(trafficCaseInfo.getId());
            if (casePerson != null) {
                caseVO.setCarType(casePerson.getCarType()!=null?casePerson.getCarType():"");
            } else {
                caseVO.setCarType("不详");
            }
            Map<String, Integer> injury = injuryMap.get(trafficCaseInfo.getId());
            if (injury != null) {
                StringBuilder stringBuilder = new StringBuilder();
                for (Map.Entry<String,Integer> entry : injury.entrySet()) {
                    stringBuilder.append(entry.getValue()).append(entry.getKey());
                }
                caseVO.setInjury(stringBuilder.toString());
            } else {
                caseVO.setInjury("不详");
            }
            caseVOS.add(caseVO);
        }
        return caseVOS;
    }

    @Override
    public TimeVO countTime(Integer adcode) {
        ArrayList<DateTime> times = new ArrayList<>();
        for (int i = 0; i < 24; i++) {
            for (int j = 0; j <= 3; j++) {
                int minute = j * 15;
                times.add(DateUtil.parse(Convert.toStr(i) + ":" + Convert.toStr(minute)));
            }
        }
        QueryWrapper<TrafficCaseInfo> queryWrapper = new QueryWrapper<>();
        if (adcode != 0) {
            Address address = this.getAddressByAdcode(adcode);
            queryWrapper.eq(this.getAddressField(address),address.getId());
        }
        List<Map<String, Object>> list = baseMapper.selectMaps(queryWrapper.select("count(*) count", "time").groupBy("time"));
        HashMap<DateTime, Integer> timeDataMap = new HashMap<>();
        for (Map<String, Object> originMap : list) {
            if (originMap.get("time") == null || originMap.get("time").equals("")) {
                continue;
            }
            DateTime dateTime = DateUtil.parse(originMap.get("time").toString());
            timeDataMap.put(dateTime,Convert.toInt(originMap.get("count")));
        }
        //x轴坐标数据
        ArrayList<String> xData = new ArrayList<>();
        ArrayList<Map<String, Object>> data = new ArrayList<>();
        for (DateTime time : times) {
            Integer i = timeDataMap.get(time);
            String timeStr = time.toString("HH:mm");
            xData.add(timeStr);
            HashMap<String, Object> dataMap = MapUtil.newHashMap();
            dataMap.put("name",timeStr);
            if (i == null) {
                dataMap.put("value",0);
            } else {
                dataMap.put("value",i);
            }
            data.add(dataMap);
        }
        TimeVO timeVO = new TimeVO();
        timeVO.setTimeData(xData);
        timeVO.setData(data);
        return timeVO;
    }

    @Override
    public List<TrafficCountVO> educationCount(Integer adcode) {
        return this.casePersonCount("education",adcode);
    }

    @Override
    public List<TrafficCountVO> windSpeedCount(Integer adcode) {
        MPJLambdaWrapper<TrafficCaseWeather> wrapper = new MPJLambdaWrapper<>();
        wrapper.select("count(*) value","wind_speed name").groupBy("wind_speed");
        if (adcode != 0) {
            Address address = this.getAddressByAdcode(adcode);
            wrapper.leftJoin(TrafficCaseInfo.class, TrafficCaseInfo::getId, TrafficCaseWeather::getCaseId)
                    .eq(getAddressField(address), address.getId());
        }
        return trafficCaseWeatherMapper.selectJoinList(TrafficCountVO.class,wrapper);
    }
}
